package server.order;

import java.sql.ResultSet;
import java.sql.SQLException;

import server.Logger;
import server.conn.ConnectionThread;
import server.db.DBObject;
import server.db.DataBase;
import server.user.User;

public class Order extends DBObject {
	public static final String TABLE = "order_log";
	public static final String ID = "id";
	public static final String USER_ID = "userId";
	public static final String CUR_1 = "cur1";
	public static final String CUR_2 = "cur2";
	public static final String RATE = "rate";
	public static final String AMOUNT = "amt";
	public static final String OPEN_TIME = "openTime";
	public static final String CLOSE_TIME = "closeTime";
	public static final String B_TYPE = "bType";
	public static final String O_TYPE = "oType";
	public static final String SPLIT = "split";
	
	public static final int BUY = 1;
	public static final int SELL = 2;
	
	public static final int ORD_MARKET = 1;
	public static final int ORD_TRAILING_STOP = 2;
	public static final int ORD_LIMIT = 3;
	
	protected int id;
	protected int userId;
	protected String cur1;
	protected String cur2;
	protected float rate;
	protected float amount;
	protected long openTime;
	protected long closeTime;
	protected int bType;
	protected int oType;
	protected int split;
	
	protected boolean switched;
	
	/**
	 * Create an order from the current row in a ResultSet
	 * @param rs	ResultSet that holds the order
	 * @throws SQLException
	 * @see ResultSet
	 */
	public Order(ResultSet rs) throws SQLException {
		super();
		fillOrderInfo(rs);
		switched = false;
	}
	
	/**
	 * Create an order from its ID
	 * @param id	OrderID
	 * @throws SQLException
	 */
	public Order(int id) throws SQLException {
		super();
		
		String query = "SELECT * FROM "+Order.TABLE+" WHERE id="+id+" LIMIT 1";
		ResultSet rs = doQuery(query);
		rs.first();
		fillOrderInfo(rs);
		switched = false;
	}
	
	/**
	 * Basic market-order constructor
	 * @param cur1		Base currency
	 * @param cur2		Quote/Counter currency
	 * @param rate		Order rate
	 * @param amount	Order amount
	 * @param bType		Buy or sell
	 * @param userId	UserID
	 */
	public Order(String cur1, String cur2, float rate, float amount, int bType, int userId) {
		this(cur1, cur2, rate, amount, bType, userId, Order.ORD_MARKET, 0, false);		
	}
	
	protected Order(String cur1, String cur2, float rate, float amount, int bType, int userId, int oType, int split, boolean switched) {
		super();
		this.cur1 = cur1;
		this.cur2 = cur2;
		this.rate = rate;
		this.amount = amount;
		this.openTime = 0;
		this.closeTime = 0;
		this.userId = userId;
		this.bType = bType;
		this.oType = oType;
		this.split = split;
		this.switched = switched;
	}
	
	/**
	 * Fill in order info from a ResultSet
	 * @param rs	The ResultSet containing the order
	 * @throws SQLException
	 */
	private final void fillOrderInfo(ResultSet rs) throws SQLException {
		id = rs.getInt(Order.ID);
		userId = rs.getInt(Order.USER_ID);
		cur1 = rs.getString(Order.CUR_1);
		cur2 = rs.getString(Order.CUR_2);
		rate = rs.getFloat(Order.RATE);
		amount = rs.getFloat(Order.AMOUNT);
		openTime = rs.getLong(Order.OPEN_TIME);
		closeTime = rs.getLong(Order.CLOSE_TIME);
		bType = rs.getInt(Order.B_TYPE);
		oType = rs.getInt(Order.O_TYPE);
		split = rs.getInt(Order.SPLIT);
	}
	
	/**
	 * Add the order to the database
	 * @throws SQLException
	 */
	protected void addToDB() throws SQLException {	
		String query = "";
		query += "INSERT INTO "+Order.TABLE+" (";
		query += Order.USER_ID+","+Order.CUR_1+","+Order.CUR_2+","+Order.RATE+","+Order.AMOUNT+","+Order.OPEN_TIME+","+Order.B_TYPE+","+Order.O_TYPE+","+Order.SPLIT+") ";
		query += "VALUES ("+userId+","+"\""+cur1+"\",\""+cur2+"\","+rate+","+amount+","+openTime+","+bType+","+oType+","+split+")";
		
		try {
			id = doUpdate(query);
		} catch (SQLException e) {
			Logger.getInstance().addSQLQueryError("Could not add order", query, 2);
			throw e;
		}
	}
	
	/**
	 * Open the order.  Set openTime and add to the database
	 * @return The orders ID
	 * @throws SQLException
	 */
	public int openOrder() throws SQLException {
		if (openTime == 0 && closeTime == 0) {
			openTime = System.currentTimeMillis();
			addToDB();
			return id;
		} else {
			return 0;
		}
	}
	
	/**
	 * Open the order but at a specific time
	 * @param time Time at which the order was "opened"
	 * @return	The new ID of the order
	 * @throws SQLException
	 */
	public int openOrder(long time) throws SQLException {
		if (openTime == 0 && closeTime == 0) {
			openTime = (time > 0) ? time : System.currentTimeMillis();
			addToDB();
			return id;
		} else {
			return 0;
		}
	}
	
	/**
	 * Close the order. Update the DB and the users accounts
	 * @throws SQLException
	 */
	public void closeOrder() throws SQLException {
		if (closeTime == 0) {
			closeTime = System.currentTimeMillis();
			String query = "UPDATE "+Order.TABLE+" SET "+Order.CLOSE_TIME+"="+closeTime+" WHERE "+Order.ID+"="+id+" LIMIT 1";
			try {
				doUpdate(query);
			} catch (SQLException e) {
				Logger.getInstance().addSQLQueryError("Could not close order", query, 2);
				throw e;
			}
			
			// TODO check the logic of this 
			User user = new User(userId);

			if (getBType() == BUY) {
				user.getAccount(getCur1()).makeChange(amount);
				user.getAccount(getCur2()).makeChange(-1*amount*getRate());
			} else {
				switchCurs();
				user.getAccount(getCur1()).makeChange(amount);
				user.getAccount(getCur2()).makeChange(-1*amount*getRate());
				switchCurs();
			}
		}
	}
	
	/**
	 * Update the database showing that the order was split
	 * @throws SQLException
	 */
	public void split() throws SQLException {
		if (closeTime == 0) {
			closeTime = System.currentTimeMillis();
			String query = "UPDATE "+Order.TABLE+" SET "+Order.SPLIT+"="+id+","+Order.CLOSE_TIME+"="+closeTime+" WHERE "+Order.ID+"="+id+" LIMIT 1";
			try {
				doUpdate(query);
			} catch (SQLException e) {
				Logger.getInstance().addSQLQueryError("Could not split order", query, 2);
				throw e;
			}
		}
	}
	
	/**
	 * Update the database showing that the order was cancelled
	 * @return	True if the operation was a success
	 */
	public boolean cancelOrder() {
		if ((closeTime == 0) && OrderController.getInstance().removeOrder(this)) {
			closeTime = System.currentTimeMillis();
			String query = "UPDATE "+Order.TABLE+" SET "+Order.SPLIT+"="+id+","+Order.CLOSE_TIME+"="+closeTime+" WHERE "+Order.ID+"="+id+" LIMIT 1";
			try {
				doUpdate(query);
				return true;
			} catch (SQLException e) {
				Logger.getInstance().addSQLQueryError("Could not cancel order", query, 2);
				return false;
			}
		} else
			return false;
	}
	
	/**
	 * Order can close (not based on the comparing order)
	 * @return		True if closeTime doesn't equal 0
	 */
	protected boolean canClose() {
		return (closeTime == 0);
	}
	
	/**
	 * Check to see if another order satisfies this order
	 * @param order		Order to check
	 * @return			True if the orders can satisfy each other
	 */
	public boolean canSatisfy(Order order) {
		if (typeCheck(order) && canClose()) {
			return (order.getRate() == getRate());
		} else
			return false;
	}
	
	/**
	 * Check to see if another order has the opposite buy/sell type
	 * @param order		Comparing order
	 * @return			True if the orders have opposite buy/sell types
	 */
	protected boolean typeCheck(Order order) {
		return (order.getBType() != getBType());
	}
	
	/**
	 * Make order appear to have switched currencies
	 */
	public void switchCurs() {
		if (!switched)
			switched = true;
		else
			switched = false;
	}
	
	public int getID() { return id; }
	public int getUserId() { return userId; }
	public String getCur1() { return (switched) ? cur2 : cur1; }
	public String getCur2() { return (switched) ? cur1 : cur2; }
	public String getRatio() { return getCur1()+getCur2(); }
	public float getRate() { return (switched) ? 1/rate : rate; }
	public float getAmount() { return amount; }
	public long getOpenTime() { return openTime; }
	public long getCloseTime() { return closeTime; }
	public int getBType() {
		if (bType == Order.BUY)
			return (switched) ? Order.SELL : Order.BUY;
		else
			return (switched) ? Order.BUY : Order.SELL;
	}
	public int getOType() { return oType; }
	public boolean isSwitched() { return switched; }
	
	/**
	 * Return string representation for sending over socket
	 */
	public String toString() {
		String split = ConnectionThread.SPLIT;
		return id+split+userId+split+cur1+split+cur2+split+rate+split+amount+split+openTime+split+closeTime+split+bType+split+oType+split+this.split;
	}
	
	/**
	 * Return the proper order from the database
	 * @param orderId	ID of the order
	 * @return		Order object
	 * @throws SQLException
	 */
	public static Order getOrder(int orderId) throws SQLException {
		String query = "SELECT * FROM "+TABLE+" WHERE "+ID+"="+orderId+" LIMIT 1";
		ResultSet rs = DataBase.getInstance().doQuery(query);
		rs.first();
		
		return getOrderFromRS(rs);
	}
	
	/**
	 * Create an order from a ResultSet
	 * @param rs	ResultSet that contains the order
	 * @return		Order object
	 * @throws SQLException
	 */
	public static Order getOrderFromRS(ResultSet rs) throws SQLException {
		Order order = null;
		switch (rs.getInt(O_TYPE)) {
		case Order.ORD_MARKET:
			order = new Order(rs);
			break;
		case Order.ORD_LIMIT:
			order = new LimitOrder(rs);
			break;
			
		case Order.ORD_TRAILING_STOP:
			order = new TrailingStopOrder(rs);
			break;
		}
		
		return order;
	}
}
